package com.mixide.ui.part;

import javax.inject.Inject;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.ui.di.UIEventTopic;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.Bullet;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.ST;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GlyphMetrics;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;

import com.mixal.software.InstructionTokenizer;
import com.mixal.software.InstructionTokenizer.State;

import lombok.SneakyThrows;

public class CodePart {
	
	private Display display;
	private CTabFolder folder;
	
	public @Inject CodePart(Display display) {
		this.display = display;
	}
	
	@PostConstruct
	public void postConstruct(Composite parent) {
		parent.setLayout(new FillLayout());
		this.folder = new CTabFolder(parent, SWT.BORDER);
	}
	
	@PreDestroy
	public void postDispose() {
		this.folder.dispose();
	}
	
	@Optional @Inject 
	private void subscribeMixFileSelected(@UIEventTopic("MIX-FILE-SELECTED") File mixFile) throws IOException {
		CTabItem mixItem = this.findItem(mixFile.getAbsolutePath())
				.orElseGet(() -> this.initItem(mixFile));
		this.folder.setSelection(mixItem);
	}
	
	private java.util.Optional<CTabItem> findItem(String id) {
		return Stream.of(this.folder.getItems())
				.filter(item -> id.equals(item.getData("ITEM_ID")))
				.findFirst();
	}
	
	private CTabItem initItem(File mixFile) {
		CTabItem mixItem = new CTabItem(this.folder, SWT.CLOSE);
		mixItem.setData("ITEM_ID", mixFile.getAbsolutePath());
		mixItem.setText(mixFile.getName());
		StyledText mixText = this.initText(mixFile);
		mixItem.setControl(mixText);
		return mixItem;
	}
	
	@SneakyThrows
	private StyledText initText(File mixFile) {
		StyledText text = new StyledText(this.folder, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		text.setFont(new Font(this.display, "Consolas", 11, SWT.NONE));
		text.setAlwaysShowScrollBars(false);
		text.addLineStyleListener(e -> {
			List<StyleRange> styles = new ArrayList<>();
			StyleRange style = this.lineStyle(e.lineOffset, e.lineText);
			if (Objects.nonNull(style)) {
				styles.add(style);
			}
			StyleRange style2 = new StyleRange();
			e.bulletIndex = text.getLineAtOffset(e.lineOffset);
			style2.foreground = CodePart.this.display.getSystemColor(SWT.COLOR_DARK_GRAY);
			style2.metrics = new GlyphMetrics(0, 0, Integer.toString(text.getLineCount() + 1).length() * 12);
			e.bullet = new Bullet(ST.BULLET_NUMBER, style2);
			e.styles = styles.toArray(new StyleRange[0]);
		});
		String code = Files.readString(mixFile.toPath());
		text.setText(code);
		text.setData("FOCUS_LINE", -1);
		text.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseDown(MouseEvent e) {
				if (e.button != 1) {
					return;
				}
				changeLineBackground(text);
			}
		});
		text.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				if (e.keyCode != SWT.ARROW_UP && e.keyCode != SWT.ARROW_DOWN && e.keyCode != SWT.ARROW_LEFT && e.keyCode != SWT.ARROW_RIGHT && e.keyCode != SWT.CR) {
					return;
				}
				changeLineBackground(text);
			}
		});
		return text;
	}
	
	private StyleRange lineStyle(int lineOffset, String lineText) {
		InstructionTokenizer instructionTokenizer = new InstructionTokenizer(new StringReader(lineText));
		
		State state = State.START;
		int start = -1, length = 0;
		int index = 0;
		while (index < lineText.length()) {
			int ch = lineText.charAt(index);
			state = state.transit(instructionTokenizer, ch);
			if (state == State.OP) {
				start = start >= 0 ? start : index;
				length++;
			} else if (length > 0) {
				break;
			}
			index++;
		}
		
		if (start == -1) {
			return null;
		}
		StyleRange style = new StyleRange();
		style.start = lineOffset + start;
		style.length = length;
		style.fontStyle = SWT.BOLD;
		style.foreground = this.display.getSystemColor(SWT.COLOR_DARK_RED);
		return style;
	}
	
	private void changeLineBackground(StyledText text) {
		
		int current = (int) text.getData("FOCUS_LINE");
		Point point = text.getSelection();
		if (point.x == point.y) {
			int line = text.getLineAtOffset(point.x);
			if (line != current) {
				if (current >= 0) {
					text.setLineBackground(current, 1, null);
				}
				text.setData("FOCUS_LINE", line);
				text.setLineBackground(line, 1, CodePart.this.display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW));
			}
		} else {
			if (current >= 0) {
				text.setLineBackground(current, 1, null);
			}
			text.setData("FOCUS_LINE", -1);
		}
	}
}